/**
 * CommunicationRemoteImpl.java
 *
 * @author Beatrice Bacelli
 * @author Luca Bedogni
 * @author Silvia Righini
 */
package unibo.lsb.communication;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.JOptionPane;

import unibo.lsb.exception.TokioException;
import unibo.lsb.graphic.ConfigurationConstGraphics;
import unibo.lsb.graphic.GameTableWindow;
import unibo.lsb.graphic.GameTableWindowSingleton;
import unibo.lsb.graphic.GraphicUtils;
import unibo.lsb.graphic.MessagesGenerator;
import unibo.lsb.graphic.RegistrationWindowSingleton;
import unibo.lsb.logic.CoveredDice;
import unibo.lsb.logic.LastAssignedPenalty;
import unibo.lsb.logic.Player;
import unibo.lsb.logic.PlayerId;
import unibo.lsb.logic.PlayerSelf;
import unibo.lsb.logic.PlayerSelfSingleton;
import unibo.lsb.logic.PlayersTable;
import unibo.lsb.server.Game;

public class CommunicationRemoteImpl extends UnicastRemoteObject implements
		CommunicationRemote {

	Game receivedGame;

	public CommunicationRemoteImpl() throws RemoteException {
		super();
	}

	@Override
	public boolean isAlive() throws RemoteException {
		return true;
	}

	@Override
	public boolean receiveGameDetails(Game game) throws RemoteException {
		// setting game
		InformationSingleton.getInstance().setGame(game);

		// computing and setting turn
		PlayerSelfSingleton pss = PlayerSelfSingleton.getInstance();

		int myturn = computeMyTurn(game, pss.getSelf());
		pss.getSelf().setIndexInTable(myturn);

		// launching Polling execution
		CommunicationLocal.executePolling();

		// launching graphics and setting notification dialog not visible
		RegistrationWindowSingleton.getInstance().getRegistrationWindow()
				.setNotificationDialogNotVisible();
		launchGraphic(game, pss.getSelf().getName());

		// Check if it's your turn to play - you are 0 - and play in case
		PlayerSelf p = PlayerSelfSingleton.getInstance().getSelf();
		if (p.getIndexInTable() == 0) {
			CoveredDice d = GameTableWindowSingleton.getInstance()
					.getGameTablePanel().castDice(p, null);
			callToSendDiceCore(d);
		}
		return true;
	}

	/**
	 * This method does all the hard work in sending dice..
	 * 
	 * @param cd
	 * @return
	 */
	private boolean callToSendDiceCore(CoveredDice cd) {
		boolean b = false;
		try {
			PlayersTable pt = InformationSingleton.getInstance().getpt();
			Player player = pt.getNextAlivePlayer();
			GameTableWindowSingleton.getInstance().getGameTablePanel()
					.refreshPanels(pt);
			PlayerId self = PlayerSelfSingleton.getInstance().getSelf();
			boolean lastAlive = GraphicUtils.checkLastAlive();
			if (!lastAlive) {
				GameTableWindowSingleton.getInstance().getGameTablePanel()
						.addMessage(
								MessagesGenerator
										.youCastAndDeclareDiceToPlayer(cd,
												player.getPlayerId()));
				// sending message to everyone but the person you are sending
				// the dice to and yourself
				List<PlayerId> excludedList = new ArrayList<PlayerId>();
				excludedList.add(player.getPlayerId());
				CommunicationLocal.sendMessage(MessagesGenerator
						.playerDeclaresDiceMessageToPlayer(self, cd, player
								.getPlayerId()), excludedList);
				// sending dice only now, or it might delay the messages
				b = CommunicationLocal.sendDice(cd, player);
			}

		} catch (TokioException e) {
			e.printStackTrace();
		}
		return b;
	}

	/**
	 * This function launches the graphics for the game. Should not be called
	 * more than once.
	 * 
	 * @param game
	 *            the game that is about to start
	 * @param playerName
	 *            the name of my player
	 */
	private void launchGraphic(Game game, String playerName) {
		GameTableWindow gt = GameTableWindowSingleton.getInstance()
				.getGameTablePanel();
		gt.setTitle(game.getName() + " - " + playerName);
		PlayersTable t = gt.setGame(game);
		gt.setTurnToPlayer(0);
		gt.setVisible(true);
		gt.refreshPanels(t);

		RegistrationWindowSingleton.getInstance().getRegistrationWindow()
				.setNotificationDialogNotVisible();
	}

	/**
	 * This method computes the turn of a player in a given game.
	 * 
	 * @param game
	 *            a game
	 * @param player
	 *            a player
	 * @return number of my own turn
	 */

	private int computeMyTurn(Game game, PlayerSelf player) {
		int turn = -1;
		boolean b = false;
		List<PlayerId> l = game.getListOfPlayers();
		Iterator<PlayerId> i = l.iterator();
		while (i.hasNext() && !b) {
			turn++;
			PlayerId pi = i.next();
			if (player.equals(pi)) {
				b = true;
			}
		}
		return turn;
	}

	public boolean receiveTurn(int turn) throws RemoteException {
		final int thisTurn = turn;
		final PlayerId playerSelf = PlayerSelfSingleton.getInstance().getSelf();
		Thread t = new Thread() {
			@Override
			public void run() {
				PlayersTable pt = InformationSingleton.getInstance().getpt();
				if (pt == null) {
					System.err.println("PlayersTable null");
				} else {
					pt.setTurn(thisTurn);
				}
				GameTableWindow gt = GameTableWindowSingleton.getInstance()
						.getGameTablePanel();

				gt.setTurnToPlayer(thisTurn);
				gt.refreshPanels(pt);
				boolean lastAlive = GraphicUtils.checkLastAlive();
				if (!lastAlive) {

					if (pt.getTurnForPlayer(playerSelf) == thisTurn) {
						// my turn
						Player nextplayer = pt.getNextAlivePlayer(pt
								.getTurnForPlayer(playerSelf));

						if (!lastAlive) {
							CoveredDice cd = gt.castDice(playerSelf, null);
							callToSendDiceCore(cd);
						}
					}
				}
			}
		};
		t.start();
		return true;
	}

	/**
	 * Who gets the penalty gets the turn as well
	 */
	@Override
	public boolean receivePenalty(LastAssignedPenalty lastAssignedPenalty)
			throws RemoteException {
		final LastAssignedPenalty lap = lastAssignedPenalty;
		Thread t = new Thread() {
			@Override
			public void run() {
				boolean retRefresh;
				PlayersTable pt = InformationSingleton.getInstance().getpt();
				PlayerId playerSelf = PlayerSelfSingleton.getInstance()
						.getSelf();

				// update playersTable
				retRefresh = refreshPenalty(lap, true);

				GameTableWindow gt = GameTableWindowSingleton.getInstance()
						.getGameTablePanel();

				// if player who got the penalty exceed it, set him lost
				if (retRefresh == false) {
					gt.setLost(pt.getTurnForPlayer(lap.getPlayer()));
					if (playerSelf.equals(lap.getPlayer())) {
						gt.addMessage(MessagesGenerator.outOfTheGame());
					} else {
						gt.addMessage(MessagesGenerator.outOfTheGame(lap
								.getPlayer()));
					}
				}

				// I took the penalty
				if (playerSelf.equals(lap.getPlayer())) {

					if (retRefresh == false) {
						// i am OUT OF GAME for reaching the maximum penalties
						GraphicUtils.displayDialog(gt,
								"You've exeeded maximum penalties",
								"Exeeding Penalties - Game Over",
								JOptionPane.ERROR_MESSAGE,
								GraphicUtils.DO_NOTHING_ON_CLOSE);
						Player nextplayer = pt.getNextAlivePlayer(pt
								.getTurnForPlayer(playerSelf));
						pt.setTurn(pt.getTurnForPlayer(nextplayer));
						CommunicationLocal.notifyTurnToAll(nextplayer
								.getPlayerId());
					} else {
						pt.setTurn(InformationSingleton.getpt()
								.getTurnForPlayer(playerSelf));

						gt.setTurnToPlayer(InformationSingleton.getpt()
								.getTurnForPlayer(playerSelf));
						gt.refreshPanels(pt);

						// send to all that is my turn
						CommunicationLocal.notifyTurnToAllBut(playerSelf);

						// notification window
						gt.showYourPenalty(lap.getPenalty());
						// cast again
						gt.refreshPanels();
						CoveredDice cd = gt.castDice(playerSelf, null);
						callToSendDiceCore(cd);
					}
				} else {
					// before i added the penalty notification, but it's better
					// if i send it remotely -order is maintained
				}
			}
		};
		t.start();
		return true;
	}

	/**
	 * Calculates penalty, updates my own PlayersTable and turn as well.
	 * 
	 * @param lastAssignedPenalty
	 * @param true if i also have to refresh the turn
	 * @return false if penalty assigned exceed the maximum penalty
	 */
	private boolean refreshPenalty(LastAssignedPenalty lastAssignedPenalty,
			boolean turn) {
		boolean ret = true;
		GameTableWindow gt = GameTableWindowSingleton.getInstance()
				.getGameTablePanel();
		PlayersTable pt = InformationSingleton.getInstance().getpt();

		boolean b = pt.computePenalty(lastAssignedPenalty);
		// the penalty assigned is over the maximum
		if (b == false) {
			ret = false;
		}
		if (turn) {
			int n = pt.getTurnForPlayer(lastAssignedPenalty.getPlayer());
			pt.setTurn(n);
		}
		gt.refreshPanels(pt);
		return ret;
	}

	@Override
	public boolean receiveDice(CoveredDice cd) throws RemoteException,
			TokioException {
		final CoveredDice coveredDice = cd;
		Thread t = new Thread() {
			@Override
			public void run() {

				PlayerSelf ps = PlayerSelfSingleton.getInstance().getSelf();
				boolean overMax;
				GameTableWindow gt = GameTableWindowSingleton.getInstance()
						.getGameTablePanel();
				int myturn = ps.getIndexInTable();
				PlayersTable pt = InformationSingleton.getInstance().getpt();

				// updating graphics and PlayersTable
				pt.setTurn(myturn);
				gt.setTurnToPlayer(myturn);
				gt.refreshPanels();

				gt.addMessage(MessagesGenerator
						.playerDeclaresDiceToYou(coveredDice));

				// send to all that is my turn
				CommunicationLocal.notifyTurnToAllBut(PlayerSelfSingleton
						.getInstance().getSelf());

				// show appropriate dialog
				int returnValue = gt.showAcceptOrDoubtDialog(coveredDice
						.getPlayerId(), coveredDice.getDeclared());
				boolean lastAlive = GraphicUtils.checkLastAlive();
				if (!lastAlive) {
					// Dice accepted
					if (returnValue == ConfigurationConstGraphics.DICE_ACCEPTED) {
						GameTableWindowSingleton.getInstance()
								.getGameTablePanel().addMessage(
										MessagesGenerator
												.youAccept(coveredDice));
						CommunicationLocal
								.sendMessage(MessagesGenerator.acceptAndReCast(
										ps, coveredDice.getDeclared()));
						// Accepting dice - show new dialog
						CoveredDice cd = GameTableWindowSingleton.getInstance()
								.getGameTablePanel().castDice(ps,
										coveredDice.getDeclared());
						callToSendDiceCore(cd);
					} else if (returnValue == ConfigurationConstGraphics.DICE_21ACCEPTED) {
						LastAssignedPenalty lap = coveredDice.accept(ps);

						// letting other know what happened
						// broadcast of the penalty
						CommunicationLocal.notifyPenalty(lap);
						CommunicationLocal.sendMessage(MessagesGenerator
								.accepted21(ps.getName()));
						gt.addMessage(MessagesGenerator.accepted21());

						// Window for myself
						GraphicUtils.displayDialog(gt, MessagesGenerator
								.accepted21(), "", JOptionPane.PLAIN_MESSAGE,
								GraphicUtils.DO_NOTHING_ON_CLOSE);

						// casting -no depending dice in this case
						overMax = refreshPenalty(lap, true);
						myTurnToCastAgainAfterPenalty(overMax, gt, pt, myturn,
								ps);
					} else if (returnValue == ConfigurationConstGraphics.DICE_DOUBTED) {
						// Doubting dice
						// "You doubt the XY"
						gt.addMessage(MessagesGenerator.doubt(coveredDice
								.getDeclared()));
						// "a doubted the XY"
						CommunicationLocal.sendMessage(MessagesGenerator.doubt(
								ps, coveredDice.getDeclared()));

						LastAssignedPenalty lap = coveredDice.doubt(ps);

						// adjust my own playersTable
						overMax = refreshPenalty(lap, true);
						if (overMax == false) {
							gt.setLost(pt.getTurnForPlayer(lap.getPlayer()));

						}

						// understanding who got the penalty
						int playerType;
						if (ps.equals(lap.getPlayer())) {
							playerType = MessagesGenerator.PLAYER_SELF;
						} else {
							playerType = MessagesGenerator.PLAYER_OTHER;
						}

						// show information window for myself
						gt.showPenaltyForDoubtDice(coveredDice.getPlayerId(),
								coveredDice, lap, playerType);

						// letting other know what happened
						// broadcast of the penalty
						CommunicationLocal.notifyPenalty(lap);

						// notifying what was covered
						List<PlayerId> excludedList = new ArrayList<PlayerId>();
						excludedList.add(lap.getPlayer());

						if (!coveredDice.getPlayerId().equals(ps)) {
							excludedList.add(coveredDice.getPlayerId());
						}

						CommunicationLocal.sendMessage(MessagesGenerator
								.xDeclaredDiceButMadeOtherDice(
										MessagesGenerator.PLAYER_OTHER,
										coveredDice), excludedList);

						// notify penalty
						CommunicationLocal.sendMessage(MessagesGenerator
								.penaltyForDoubting(
										MessagesGenerator.PLAYER_OTHER, lap),
								excludedList);

						if (!pt.getPlayer(pt.getTurnForPlayer(lap.getPlayer()))
								.isAlive()) {
							CommunicationLocal.notifyTurnToAll(ps);
						}

						// if it's me who got the penalty, then it's me the one
						// who
						// casts again
						if (playerType == MessagesGenerator.PLAYER_SELF) {
							myTurnToCastAgainAfterPenalty(overMax, gt, pt,
									myturn, ps);
						} else {
							// not my turn to cast
						}
					}
				}
			}

		};
		t.start();
		return true;
	}

	private void myTurnToCastAgainAfterPenalty(boolean overMax,
			GameTableWindow gt, PlayersTable pt, int myturn, PlayerSelf ps) {
		if (overMax == false) {
			// out of game for reaching the maximum
			// penalties
			GraphicUtils
					.displayDialog(gt, "You've exeeded maximum penalties",
							"Exeeding Penalties - Game Over",
							JOptionPane.ERROR_MESSAGE,
							GraphicUtils.DO_NOTHING_ON_CLOSE);
			Player nextplayer = pt.getNextAlivePlayer(myturn);
			pt.setTurn(pt.getTurnForPlayer(nextplayer));
			CommunicationLocal.notifyTurnToAll(nextplayer.getPlayerId());
		} else {
			// casting -no depending dice in this case
			CoveredDice newCd = gt.castDice(ps, null);
			callToSendDiceCore(newCd);
		}
	}

	@Override
	public boolean receiveMsg(String msg) throws RemoteException,
			TokioException {
		GameTableWindowSingleton.getInstance().getGameTablePanel().addMessage(
				msg);
		return true;
	}

	@Override
	public boolean receiveStatus(int playerNumber, boolean alive)
			throws RemoteException, TokioException {

		PlayersTable pt = InformationSingleton.getInstance().getpt();
		Player playerLoc = pt.getPlayer(playerNumber);

		if (alive == false)
			playerLoc.setStatus(1);
		else
			playerLoc.setStatus(0);

		// System.out.println(pt.toString());

		return false;
	}

	@Override
	public boolean receiveWinning(int playerNumber) throws RemoteException,
			TokioException {
		int myIndex = PlayerSelfSingleton.getInstance().getSelf()
				.getIndexInTable();
		int myPenalties = InformationSingleton.getpt().getPlayer(myIndex)
				.getPenalty();
		if (myPenalties != PlayersTable.MAX_PENALTIES) {
			throw new RuntimeException("Houston we have a problem! Player "
					+ myIndex + " has " + myPenalties
					+ " yet was notified that " + playerNumber + " won.");
		} else {
			String name = InformationSingleton.getpt().getPlayer(playerNumber)
					.getPlayerId().getName();
			GameTableWindow gt = GameTableWindowSingleton.getInstance()
					.getGameTablePanel();
			gt.addMessage(MessagesGenerator.winner(name));
			GraphicUtils.displayDialog(gt, MessagesGenerator.winner(name),
					MessagesGenerator.gameOver(), JOptionPane.PLAIN_MESSAGE,
					GraphicUtils.BACK_TO_MAIN_ON_CLOSE);
			GameTableWindowSingleton.closeGame();
		}
		return false;
	}
}
